﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection.Interception.Interface;
using MugenInjection.Interception.Proxy;
using Should;

namespace MugenInjectionTest.Interception
{
    public abstract class TestClass
    {
        public abstract void TestAbstract();

        public virtual void TestVirtual()
        {

        }

        protected abstract void TestProtected();

        internal virtual void TestInternal()
        {

        }

        internal protected abstract void TestProtectedInternal();

        public void Test()
        {
            TestProtected();
            TestProtectedInternal();
        }
    }

    public class TestRefOutClass
    {
        public virtual void TestRefMethod(ref string value, ref int number)
        {
        }

        public virtual void TestOutMethod(out string value, out int number)
        {
            value = "test";
            number = 0;
        }
    }

    public abstract class TestConstructorAbstract
    {
        protected TestConstructorAbstract(string prot)
        {

        }

        protected internal TestConstructorAbstract(string arg1, string arg2)
        {

        }

        internal TestConstructorAbstract(string arg1, string arg2, string arg3)
        {

        }

        private TestConstructorAbstract(int arg1)
        {

        }
    }

    public class TestConstructor
    {
        public TestConstructor()
        {

        }

        protected TestConstructor(string prot)
        {

        }

        protected internal TestConstructor(string arg1, string arg2)
        {

        }

        internal TestConstructor(string arg1, string arg2, string arg3)
        {

        }

        private TestConstructor(int arg1)
        {

        }
    }

    [TestClass]
    public class CreateInstanceTest : InterceptionTestBase
    {
        [TestMethod]
        public void CreateProxyTest()
        {
            var proxyFactory = GetProxyFactory();
            Type proxy;
            proxyFactory.CreateProxy(typeof(List<string>), out proxy);
            typeof(List<string>).IsAssignableFrom(proxy).ShouldBeTrue();

            proxyFactory.CreateProxy(typeof(List<string>),
                                     new Type[] { typeof(IDisposable), typeof(IDictionary<string, string>) }, out proxy);
            typeof(List<string>).IsAssignableFrom(proxy).ShouldBeTrue();
            typeof(IDisposable).IsAssignableFrom(proxy).ShouldBeTrue();
            typeof(IDictionary<string, string>).IsAssignableFrom(proxy).ShouldBeTrue();

            proxyFactory.CreateProxy(new Type[] { typeof(IDisposable), typeof(IDictionary<string, string>), typeof(IList<CreateInstanceTest>) }, out proxy);
            typeof(IDisposable).IsAssignableFrom(proxy).ShouldBeTrue();
            typeof(IDictionary<string, string>).IsAssignableFrom(proxy).ShouldBeTrue();
            typeof(IList<CreateInstanceTest>).IsAssignableFrom(proxy).ShouldBeTrue();
        }

        [TestMethod]
        public void CacheTest()
        {
            IProxyFactory proxyFactory = GetProxyFactory();
            Type proxy, proxy1;

            proxyFactory.CreateProxy(typeof(object), out proxy);
            proxyFactory.CreateProxy(typeof(object), out proxy1);
            proxy.ShouldEqual(proxy1);

            proxyFactory.CreateProxy(new[] { typeof(IDisposable), typeof(IList<string>), typeof(IDictionary<string, object>) }, out proxy);
            proxyFactory.CreateProxy(new[] { typeof(IDisposable), typeof(IList<string>), typeof(IDictionary<string, object>) }, out proxy1);
            proxy.ShouldEqual(proxy1);
        }

        [TestMethod]
        public void CreateProxyCollectionTest()
        {
            var proxyFactory = GetProxyFactory();
            Type proxy;
            //Class
            var proxyCollection = proxyFactory.CreateProxy(typeof(object), out proxy)();
            proxyCollection.Count.ShouldEqual(3);

            proxyCollection = proxyFactory.CreateProxy(typeof(TestClass), out proxy)();
            proxyCollection.Count.ShouldEqual(8);

            //Interface
            proxyCollection = proxyFactory.CreateProxy(new Type[] { typeof(IDisposable) }, out proxy)();
            proxyCollection.Count.ShouldEqual(4);

            //Interface and class
            proxyCollection = proxyFactory.CreateProxy(typeof(TestClass), new Type[] { typeof(IDisposable) }, out proxy)();
            proxyCollection.Count.ShouldEqual(9);
        }

        [TestMethod]
        public void TestCreateClassInstance()
        {
            var proxyFactory = GetProxyFactory();
            Type proxy;

#if !NETFX_CORE
            const BindingFlags bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public;
#endif

            //Class
            var proxyCollection = proxyFactory.CreateProxy(typeof(TestClass), out proxy)();

#if NETFX_CORE
            var testAbstract = (MethodProxy)proxyCollection.Find(typeof(TestClass).GetMethod("TestAbstract"));
            var testVirtual = (MethodProxy)proxyCollection.Find(typeof(TestClass).GetMethod("TestVirtual"));
            var testProtected = (MethodProxy)proxyCollection.Find(typeof(TestClass).GetMethod("TestProtected"));
            var testProtectedInternal = (MethodProxy)proxyCollection.Find(typeof(TestClass).GetMethod("TestProtectedInternal"));
#else
            var testAbstract = (MethodProxy)proxyCollection.Find(typeof(TestClass).GetMethod("TestAbstract", bindingFlags));
            var testVirtual = (MethodProxy)proxyCollection.Find(typeof(TestClass).GetMethod("TestVirtual", bindingFlags));
            var testProtected = (MethodProxy)proxyCollection.Find(typeof(TestClass).GetMethod("TestProtected", bindingFlags));
            var testProtectedInternal = (MethodProxy)proxyCollection.Find(typeof(TestClass).GetMethod("TestProtectedInternal", bindingFlags));
#endif
            var instance = (TestClass)Activator.CreateInstance(proxy, new object[] { proxyCollection });

            bool ab = false, virt = false, prt = false, ptrInter = false;
            testAbstract.OnInvoke = () => ab = true;
            testVirtual.OnInvoke = () => virt = true;
            testProtected.OnInvoke = () => prt = true;
            testProtectedInternal.OnInvoke = () => ptrInter = true;

            instance.Test();
            instance.TestAbstract();
            instance.TestInternal();
            instance.TestVirtual();

            ab.ShouldBeTrue();
            virt.ShouldBeTrue();
            prt.ShouldBeTrue();
            ptrInter.ShouldBeTrue();
        }

        [TestMethod]
        public void TestRefParameters()
        {
            var proxyFactory = GetProxyFactory();
            Type proxy;
            var proxyCollection = proxyFactory.CreateProxy(typeof(TestRefOutClass), out proxy)();
            var testRefMethod = (MethodProxy)proxyCollection.Find(typeof(TestRefOutClass).GetMethod("TestRefMethod"));
            var testOutMethod = (MethodProxy)proxyCollection.Find(typeof(TestRefOutClass).GetMethod("TestOutMethod"));
            var instance = (TestRefOutClass)Activator.CreateInstance(proxy, new object[] { proxyCollection });

            string param1 = "test";
            int param2 = -1;
            testRefMethod.OnInvoke = () =>
                {
                    param1.ShouldEqual(testRefMethod.InputParameters[0]);
                    param2.ShouldEqual(testRefMethod.InputParameters[1]);

                    testRefMethod.InputParameters[0] = "ref";
                    testRefMethod.InputParameters[1] = 1000;
                };
            instance.TestRefMethod(ref param1, ref param2);
            param1.ShouldEqual("ref");
            param2.ShouldEqual(1000);

            param1 = "test";
            param2 = -1;
            testOutMethod.OnInvoke = () =>
                {
                    param1.ShouldEqual(testOutMethod.InputParameters[0]);
                    param2.ShouldEqual(testOutMethod.InputParameters[1]);
                    testOutMethod.Method();
                    testOutMethod.InputParameters[0].ShouldEqual("test");
                    testOutMethod.InputParameters[1].ShouldEqual(0);

                    testOutMethod.InputParameters[0] = "out";
                    testOutMethod.InputParameters[1] = 10000;
                };
            instance.TestOutMethod(out param1, out param2);
            param1.ShouldEqual("out");
            param2.ShouldEqual(10000);
        }
    }
}